//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<FrameComposition.cpp>
///	@path	~/src/appear/xwin/
///	@date	2008/10/11
///	@desc	.

#define _FULLAPI
#include "config/config.h"

#include "lib/utilities/observer.h"

#include "graphics/IRenderer.h"

#include "gui/xgui/XFrame.h"
#include "gui/composite/CCompositeTexture.h"

#include "appear/xwin/IGuiEffect.h"
#include "appear/xwin/FrameComposition.h"


namespace xeres {
	
	// FrameComposition
	FrameComposition::FrameComposition( void )
		: m_invertResX( 0 )
		, m_invertResY( 0 )
		, m_enableAero( true )
	{
	}
	// ~FrameComposition
	FrameComposition::~FrameComposition( void )
	{
	}
	// Initialize
	void FrameComposition::Initialize( RenderContext& rc , RefWeak<IEffect> effect )
	{
		// Create effects.
		m_effect = effect;
		
		m_frameTech = m_effect->GetTechnique( "RenderFrame" );
		m_downsampleTech = m_effect->GetTechnique( "RenderDownsample" );
		m_aeroTech = m_effect->GetTechnique( "RenderAero" );

		m_aeroParam = m_effect->GetVariableByName( "BlurParam" );
		m_aeroScrSize = m_effect->GetVariableByName( "InvScreenSize"	);
		m_downsampleOffsetVar = m_effect->GetVariableByName( "DownSampleOffsets" );
		m_frameTextureVar = m_effect->GetVariableByName( "FrameTexture" );
		m_blurTextureVar = m_effect->GetVariableByName( "BlurTexture" );

		m_quadLayout = rc.GetRenderer()->DeclareVertexLayout( vd::ScreenTexture::decl() );
		m_frameAdapter = rc.GetRenderer()->LinkEffect( m_frameTech , m_quadLayout );
		m_downSampleAdapter = rc.GetRenderer()->LinkEffect( m_downsampleTech , m_quadLayout );
		m_aeroAdapter = rc.GetRenderer()->LinkEffect( m_aeroTech , m_quadLayout );

		m_desktopQuad.Initialize( rc , m_quadLayout );
		m_frameQuad.Initialize( rc , m_quadLayout );

	}
	// Finalize
	void FrameComposition::Finalize( void )
	{
		m_desktopQuad.Finalize();
		m_frameQuad.Finalize();
	}
	// BeginFrame
	void FrameComposition::BeginFrame( RenderContext& rc )
	{
		// Apply state block.
// 		if( m_desktopState->RequireCapture() )
// 		{
			D3DXMATRIXA16 mat;
			D3DXMatrixIdentity( &mat );
			if( rc.GetDX9() )
			{
				rc.GetDX9()->SetTransform( D3DTS_VIEW , &mat );
				rc.GetDX9()->SetTransform( D3DTS_WORLD , &mat );
				rc.GetDX9()->SetTransform( D3DTS_PROJECTION , &mat );

				rc.GetDX9()->SetRenderState( D3DRS_ZENABLE, D3DZB_FALSE );

				rc.GetDX9()->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
				rc.GetDX9()->SetRenderState( D3DRS_BLENDOP , D3DBLENDOP_ADD );
				rc.GetDX9()->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
				rc.GetDX9()->SetRenderState( D3DRS_SRCBLEND , D3DBLEND_SRCALPHA );
				rc.GetDX9()->SetRenderState( D3DRS_DESTBLEND , D3DBLEND_INVSRCALPHA );
				rc.GetDX9()->SetRenderState( D3DRS_ALPHATESTENABLE , TRUE );

				// alpha+ (1-alpha) * al
				rc.GetDX9()->SetRenderState( D3DRS_SEPARATEALPHABLENDENABLE , TRUE );
				rc.GetDX9()->SetRenderState( D3DRS_BLENDOPALPHA , D3DBLENDOP_ADD );
				rc.GetDX9()->SetRenderState( D3DRS_SRCBLENDALPHA , D3DBLEND_ONE );
				rc.GetDX9()->SetRenderState( D3DRS_DESTBLENDALPHA , D3DBLEND_INVSRCALPHA );

				rc.GetDX9()->SetRenderState( D3DRS_COLORWRITEENABLE ,
					D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA );

				rc.GetDX9()->SetRenderState( D3DRS_ZWRITEENABLE , FALSE );

				rc.GetDX9()->SetRenderState( D3DRS_FILLMODE,   D3DFILL_SOLID );
				rc.GetDX9()->SetRenderState( D3DRS_CULLMODE,   D3DCULL_CCW );

				rc.GetDX9()->SetRenderState( D3DRS_STENCILENABLE,    FALSE );
				rc.GetDX9()->SetRenderState( D3DRS_CLIPPLANEENABLE,  FALSE );
				rc.GetDX9()->SetRenderState( D3DRS_FOGENABLE,        FALSE );

				rc.GetDX9()->SetSamplerState( 0 , D3DSAMP_MINFILTER , D3DTEXF_POINT );
				rc.GetDX9()->SetSamplerState( 0 , D3DSAMP_MAGFILTER , D3DTEXF_POINT );
				rc.GetDX9()->SetSamplerState( 1 , D3DSAMP_MINFILTER , D3DTEXF_LINEAR );
				rc.GetDX9()->SetSamplerState( 1 , D3DSAMP_MAGFILTER , D3DTEXF_LINEAR );
			}
			else // dx10
			{
			}
// 			m_desktopState->Capture();
// 		}
// 		else
// 		{
// 			if( rc.GetDX9() )
// 				m_desktopState->Apply();
// 		}
		
		m_quadLayout->ApplyLayout( rc );

		// Apply desktop render target.
		m_desktopRtSet->ApplyTarget( rc );
		
		// restore viewport.
		rc.RestoreViewport();

		// clear target.
		rc.ClearColorAndDepth( PackColor4(0L) , 1.0f );
	}
	// EndFrame
	void FrameComposition::EndFrame( RenderContext& rc )
	{
		// Clear texture states.
		for( int i = 0 ; i < 3 ; ++i )
			rc.GetRenderer()->SetTexture( SHADER_PS , i , NULL );

		// Draw desktop to screen.
		rc.GetRenderer()->RestoreDefaultRenderTarget();
		
		m_frameTextureVar->SetByTexture( m_desktopRt );
		{
			m_frameAdapter->AssembleShader( rc );
			m_desktopQuad.Establish( rc );
			UINT pass = 0;
			do 
			{
				m_frameTech->Begin( pass , rc );
				m_desktopQuad.Draw( rc , 0 , 1 );
				m_frameTech->End( pass , rc );
			}while(pass);
		}
		// Clear texture state.
		m_frameTextureVar->SetByTexture( NULL );

		m_dirtyRegions.clear();
	}
	// Resize
	void FrameComposition::Resize( RenderContext& rc , const Pair<int>& size )
	{
		XS_ASSERT( size.x > 0 && size.y > 0 );

		if( m_desktopState.IsNull() )
		{
			m_desktopState = rc.GetRenderer()->CreateStateForAll();
		}

		// Re-create render target.
		if( m_desktopRt.IsValid() ) rc.GetRenderer()->FreeDeviceResource( m_desktopRt );
		if( m_downSampleRt.IsValid() ) rc.GetRenderer()->FreeDeviceResource( m_downSampleRt );

		m_desktopRt = rc.GetRenderer()->CreateColorRenderTarget( size.x , size.y );
		m_downSampleRt = rc.GetRenderer()->CreateColorRenderTarget( rc.ResX<int>() / 4 , rc.ResY<int>() / 4 );

		m_desktopRtSet = rc.GetRenderer()->CreateRenderTargetSet( size.x , size.y );
		m_downSampleRtSet = rc.GetRenderer()->CreateRenderTargetSet( rc.ResX<int>() / 4 , rc.ResY<int>() / 4 );

		m_desktopRtSet->BindTarget( 0 , m_desktopRt );
		m_downSampleRtSet->BindTarget( 0 , m_downSampleRt );

		const float scr[2] = { 1.f / rc.ResX<float>() * 4.f , 1.f / rc.ResY<float>() * 4.f };
		m_aeroScrSize->SetByData( scr , sizeof(scr) );

		// Reset effect parameters.
		const Pair<int> dsize = m_downSampleRt->GetSize();
		D3DXVECTOR2 dsOffsets[9];
		int idx = 0;
		for( int i = -1; i < 2; i++ )
		{
			for( int j = -1; j < 2; j++ )
			{
				dsOffsets[idx++] = D3DXVECTOR2(
					( static_cast< float >( i ) + 0.5f ) * ( 1.0f / static_cast< float >( dsize.x ) ),
					( static_cast< float >( j ) + 0.5f ) * ( 1.0f / static_cast< float >( dsize.y ) ));
			}
		}
		m_downsampleOffsetVar->SetByData( dsOffsets , sizeof(dsOffsets) );

		// Re-create all geometry.
		const float rx = rc.ResX<float>();
		const float ry = rc.ResY<float>();

		m_invertResX = 1 / rx;
		m_invertResY = 1 / ry;

		const float dx = float( rc.ResX<int>() / 4 );
		const float dy = float( rc.ResY<int>() / 4 );
		m_invX2 = 1 / ( dx / 2 );
		m_invY2 = 1 / ( dy / 2 );
		m_dx = (int)dx;
		m_dy = (int)dy;

		const Pair<float> pf = rc.GetRasterTextureCoord( rx , ry );
		const Pair<float> df = rc.GetRasterTextureCoord( dx , dy );

		vd::ScreenTexture vertices[] = {
			// desktop vertex
			{ 0  , 0  , 1.f , 1.f , 0.f + pf.x , 0.f + pf.y , },
			{ rx , 0  , 1.f , 1.f , 1.f + pf.x , 0.f + pf.y , },
			{ 0  , ry , 1.f , 1.f , 0.f + pf.x , 1.f + pf.y , },
			{ rx , ry , 1.f , 1.f , 1.f + pf.x , 1.f + pf.y , },
			// downsample
			{  0 ,  0 , 1.f , 1.f , 0.f + df.x , 0.f + df.y , },
			{ dx ,  0 , 1.f , 1.f , 1.f + df.x , 0.f + df.y , },
			{  0 , dy , 1.f , 1.f , 0.f + df.x , 1.f + df.y , },
			{ dx , dy , 1.f , 1.f , 1.f + df.x , 1.f + df.y , },
		};
		rc.ProcessRasterCoord( vertices , sizeof(vd::ScreenTexture) , 4 );
		rc.ProcessRasterCoord( vertices + 4 , sizeof(vd::ScreenTexture) , 4 , m_invX2 , m_invY2 );
		m_desktopQuad.Copy( vertices );
		m_desktopQuad.Commit();
	}
	// DrawFrame
	void FrameComposition::DrawFrame( RenderContext& rc , const Rect<int>& placement , RefWeak<ITexture> tex , RefWeak<IGuiEffect> effect )
	{
		const float px = float(placement.left()) , py = float(placement.top()) ,
			sx = float(placement.right()) , sy = float(placement.bottom());
		const Pair<float> df = rc.GetRasterTextureCoord( sx - px , sy - py );
		vd::ScreenTexture frame_vert[] = {
			{ px , py , 0.9f , 1.f , 0.f + df.x , 0.f + df.y , },
			{ sx , py , 0.9f , 1.f , 1.f + df.x , 0.f + df.y , },
			{ px , sy , 0.9f , 1.f , 0.f + df.x , 1.f + df.y , },
			{ sx , sy , 0.9f , 1.f , 1.f + df.x , 1.f + df.y , },
		};
		rc.ProcessRasterCoord( frame_vert , sizeof(vd::ScreenTexture) , 4 );
		m_frameQuad.Copy( frame_vert );
		m_frameQuad.Commit();

		if( effect.IsValid() )
		{
			// draw with effect
			effect->Render( rc , tex );
		}
		else
		{
			// draw directly
			m_frameTextureVar->SetByTexture( tex );
			m_frameAdapter->AssembleShader( rc );
			m_frameQuad.Establish( rc );

			UINT pass = 0;
			do 
			{
				m_frameTech->Begin( pass , rc );
				m_frameQuad.Draw( rc , 0 , 1 );
				m_frameTech->End( pass , rc );
			}while( pass != 0 );

			m_frameTextureVar->SetByTexture( NULL );
		}
		m_dirtyRegions.push_back( placement );
	}
	bool enableSave = false;
	// AeroCompositeFrame
	void FrameComposition::AeroCompositeFrame( RenderContext& rc , const Rect<int>& placement , RefWeak<ITexture> tex , const Rect<int>& aero )
	{
		if( !m_enableAero )
		{
			DrawFrame( rc , placement , tex , NULL );
			return;
		}

		// check if dirty
		bool pulluted = false;
		for( size_t i = 0 , total = m_dirtyRegions.size() ; i < total ; ++i )
		{
			if( m_dirtyRegions[i].is_overlap( placement ) )
			{
				pulluted = true;
				break;
			}
		}
		if( !pulluted )
		{
			DrawFrame( rc , placement , tex , NULL );
			return;
		}

		const float px = float(placement.left()) , py = float(placement.top()) ,
			sx = float(placement.right()) , sy = float(placement.bottom());
		const Pair<float> df = rc.GetRasterTextureCoord( sx - px , sy - py );
		const float ax = float(aero.left()) , ay = float(aero.top()) ,
			ox = float(aero.right()) , oy = float(aero.bottom()) ;
		const Pair<float> of = rc.GetRasterTextureCoord( ox - ax , oy - ay );

		vd::ScreenTexture frame_vert[] = {
			// 1
			{ ax , ay , 0.9f , 1.f , 0.f + of.x , 0.f + of.y , },
			{ ox , ay , 0.9f , 1.f , 1.f + of.x , 0.f + of.y , },
			{ ax , oy , 0.9f , 1.f , 0.f + of.x , 1.f + of.y , },
			{ ox , oy , 0.9f , 1.f , 1.f + of.x , 1.f + of.y , },
			// 2
			{ px , py , 0.9f , 1.f , 0.f + df.x , 0.f + df.y , },
			{ sx , py , 0.9f , 1.f , 1.f + df.x , 0.f + df.y , },
			{ px , sy , 0.9f , 1.f , 0.f + df.x , 1.f + df.y , },
			{ sx , sy , 0.9f , 1.f , 1.f + df.x , 1.f + df.y , },
		};
		rc.ProcessRasterCoord( frame_vert , sizeof(vd::ScreenTexture) , 8 );
		m_frameQuad.Copy( frame_vert );
		m_frameQuad.Commit();

		// Down sample desktop rt.
		rc.SetViewport( 0 , 0 , m_dx , m_dy , 0 , 1 );
		{
			// Switch to down sample rt.
			m_downSampleRtSet->ApplyTarget( rc );

			m_blurTextureVar->SetByTexture( m_desktopRt );
			m_downSampleAdapter->AssembleShader( rc );
			m_desktopQuad.Establish( rc );

			UINT pass = 0;
			do 
			{
				m_downsampleTech->Begin( pass , rc );
				m_desktopQuad.Draw( rc , 1 , 2 );
				m_downsampleTech->End( pass , rc );
			}while( pass != 0 );

			m_blurTextureVar->SetByTexture( NULL );

			// FIXME: A hack! To clear blur texture state.
			m_downsampleTech->Begin( pass , rc );
			m_downsampleTech->End( pass , rc );
		}
		rc.RestoreViewport();

		// Draw frame
		{
			// Switch to desktop rt.
			m_desktopRtSet->ApplyTarget( rc );

			m_frameTextureVar->SetByTexture( tex );
			m_blurTextureVar->SetByTexture( m_downSampleRt );

			const double w = double(aero.width());
			const double h = double(aero.height());
			float AeroParam[4] = {
				float( double(ax) * m_invertResX ) , float( double(ay) * m_invertResY ) ,
				float( w * m_invertResX ) , float( h * m_invertResY ) };

			m_aeroParam->SetByData( AeroParam , sizeof(AeroParam) );
			m_aeroAdapter->AssembleShader( rc );
			m_frameQuad.Establish( rc );

			UINT pass = 0;
			m_aeroTech->Begin( pass , rc );
			// Aero pass.
			{
				m_frameQuad.Draw( rc , 0 , 1 );
			}
			m_aeroTech->End( pass , rc );

			// Frame pass.
			m_aeroTech->Begin( pass , rc );
			{
				m_frameQuad.Draw( rc , 1 , 2 );
			}
			m_aeroTech->End( pass , rc );

			m_frameTextureVar->SetByTexture( NULL );
			m_blurTextureVar->SetByTexture( NULL );
		}

		m_dirtyRegions.push_back( placement );
	}
	
} // namespace xeres
